﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="SkinLoader.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2011 
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.GUI.
//
//   It is released under the Microsoft Public License (Ms-PL).
//
//   You should have received a copy of the Microsoft Public License
//   along with starLiGHT.GUI. If not, see http://sle.codeplex.com/license.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.GUI are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 9616                  $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2011-05-02 11:53:44 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.GUI
{
    #region Using Statements
    using System;
	using System.IO;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Linq;
    using Microsoft.Xna.Framework;
    #endregion

    public class SkinXMLLoader
    {
        #region Private Members
        private string skinFileName;

        #endregion

        public SkinXMLLoader(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException("fileName");
            }

            this.skinFileName = fileName;
        }

        public Skin Load(GuiManager manager, string filename)
        {
            this.skinFileName = filename;
			XDocument xdoc = XDocument.Load(this.skinFileName);
            return this.Load(manager);
        }
		
		public Skin Load(GuiManager manager, Stream xDocStream) // workaround to support loading with MonoGame
		{
			XDocument xdoc = XDocument.Load(xDocStream);
            return this.Load(manager, xdoc);
		}

        public Skin Load(GuiManager manager)
        {
			XDocument xdoc = XDocument.Load(this.skinFileName);
            return this.Load(manager, xdoc);
		}
		
        private Skin Load(GuiManager manager, XDocument xdoc)
        {
            Skin skin = new Skin(manager, this.skinFileName);


            foreach (XElement e in xdoc.Elements("Skin"))
            {
                foreach (XElement font in e.Elements("Font"))
                {
                    foreach (XAttribute fontNameAttribute in font.Attributes("Name"))
                    {
                        skin.AddFont(fontNameAttribute.Value);
                    }
                }

                foreach (XElement imageSet in e.Elements("Imageset"))
                {
                    skin.AddImageSet(this.LoadImageSet(skin, imageSet));
                }

                foreach (XElement layout in e.Elements("Layout"))
                {
                    skin.AddLayout(this.LoadLayout(skin, layout));
                }
            }

            return skin;
        }

        public Alignment AlignmentFromString(string alignment)
        {
            switch (alignment.ToLowerInvariant())
            {
                case "top":
                    return Alignment.Top;
                case "topleft":
                    return Alignment.TopLeft;
                case "left":
                    return Alignment.Left;
                case "bottomleft":
                    return Alignment.BottomLeft;
                case "bottom":
                    return Alignment.Bottom;
                case "bottomright":
                    return Alignment.BottomRight;
                case "right":
                    return Alignment.Right;
                case "topright":
                    return Alignment.TopRight;
            }

            return Alignment.TopLeft;
        }

        private Imageset LoadImageSet(Skin skin, XElement imageset)
        {
            Imageset imageSet = new Imageset(skin, this.GetStringAttributeValue(imageset, "Name"));

            foreach (XElement image in imageset.Elements("Image"))
            {
                imageSet.AddImage(this.LoadImage(imageSet, image));
            }

            return imageSet;
        }

        private Layout LoadLayout(Skin skin, XElement layout)
        {
            Layout layoutElement = new Layout(skin, this.GetStringAttributeValue(layout, "Name"));

            string inherits = this.GetStringAttributeValue(layout, "Inherits");
            if (!string.IsNullOrEmpty(inherits))
            {
                layoutElement.Inherits = skin.GetLayout(inherits);
                this.InheritLayoutFrom(layoutElement, skin.GetLayout(inherits));
            }

            foreach (XElement state in layout.Elements("State"))
            {
                layoutElement.AddState(this.LoadState(layoutElement, state));
            }

            foreach (XElement properties in layout.Elements("Properties"))
            {
                foreach (XElement property in properties.Elements("Property"))
                {
                    layoutElement.AddProperty(this.LoadProperty(layoutElement, property));
                }
            }

            XElement e;

            e = layout.Element("ForeColor");
            if (e != null)
            {
                layoutElement.DefaultForeColor = ColorExtensions.FromString(this.GetStringAttributeValue(e, "Color"));
            }

            e = layout.Element("BackColor");
            if (e != null)
            {
                layoutElement.DefaultBackColor = ColorExtensions.FromString(this.GetStringAttributeValue(e, "Color"));
            }

            e = layout.Element("MinimumSize");
            if (e != null)
            {
                layoutElement.MinimumSize = new Vector2(this.GetIntegerAttributeValue(e, "Width"), this.GetIntegerAttributeValue(e, "Height"));
            }

            e = layout.Element("MaximumSize");
            if (e != null)
            {
                layoutElement.MaximumSize = new Vector2(this.GetIntegerAttributeValue(e, "Width"), this.GetIntegerAttributeValue(e, "Height"));
            }

            e = layout.Element("DefaultSize");
            if (e != null)
            {
                layoutElement.DefaultSize = new Vector2(this.GetIntegerAttributeValue(e, "Width"), this.GetIntegerAttributeValue(e, "Height"));
            }
            else
            {
                layoutElement.DefaultSize = layoutElement.MinimumSize;
            }

            return layoutElement;
        }

        private void InheritLayoutFrom(Layout dest, Layout other)
        {
            if (other == null || dest == null)
            {
                return;
            }

            foreach (State s in other.States)
            {
                dest.AddState(s);
            }

            dest.DefaultForeColor = other.DefaultForeColor;
            dest.DefaultBackColor = other.DefaultBackColor;

            dest.MinimumSize = other.MinimumSize;
            dest.DefaultSize = other.DefaultSize;
        }

        private Property LoadProperty(Layout layout, XElement element)
        {
            Property prop = new Property(layout, this.GetStringAttributeValue(element, "Name"));

            string type = this.GetStringAttributeValue(element, "Type");
            string value = this.GetStringAttributeValue(element, "Value");

            switch (type.ToLower())
            {
                case "string":
                    prop.StringValue = value;
                    break;
                case "integer":
                case "int":
                    prop.IntegerValue = int.Parse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
                    break;
                case "float":
                case "single":
                    prop.FloatValue = float.Parse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
                    break;
                default:
                    throw new Exception("Unknown property type: '" + type + "' (value: '" + value + "')");
            }

            return prop;
        }

        private State LoadState(Layout layout, XElement stateElement)
        {
            State state = new State(layout, this.GetStringAttributeValue(stateElement, "Name"));

            XElement e;

            e = stateElement.Element("ForeColor");
            if (e != null)
            {
                state.ForeColor = ColorExtensions.FromString(this.GetStringAttributeValue(e, "Color"));
            }
            else
            {
                state.ForeColor = null;
            }

            e = stateElement.Element("BackColor");
            if (e != null)
            {
                state.BackColor = ColorExtensions.FromString(this.GetStringAttributeValue(e, "Color"));
            }
            else
            {
                state.BackColor = null;
            }

            foreach (XElement layer in stateElement.Elements("Layer"))
            {
                if (!layer.IsEmpty || layer.HasAttributes)
                {
                    state.AddLayer(this.LoadLayer(state, layer));
                }
            }

            return state;
        }

        private Layer LoadLayer(State state, XElement layerElement)
        {
            if (state == null)
            {
                throw new NullReferenceException("state");
            }

            if (layerElement == null)
            {
                throw new NullReferenceException("layerElement");
            }

            if (layerElement.IsEmpty && !layerElement.HasAttributes)
            {
                return null;
            }

            Layer layer = new Layer(state, this.GetStringAttributeValue(layerElement, "Name"));

            layer.Width = this.GetIntegerAttributeValue(layerElement, "Width");
            layer.Height = this.GetIntegerAttributeValue(layerElement, "Height");
            layer.Alignment = this.AlignmentFromString(this.GetStringAttributeValue(layerElement, "Alignment"));

            string imageName = this.GetStringAttributeValue(layerElement, "Image");
            if (!string.IsNullOrEmpty(imageName))
            {
                layer.Image = state.Layout.Skin.GetImageReference(imageName);

                if (layer.Image == null)
                {
                    throw new ImageNotFoundException("Could not found a image with the name '" + imageName + "' in layer '" + layer.Name + "'");
                }
            }

            foreach (XElement margins in layerElement.Elements("Margins"))
            {
                foreach (XElement margin in margins.Elements())
                {
                    switch (margin.Name.LocalName.ToLowerInvariant())
                    {
                        case "offset":
                            layer.OffsetMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "resize":
                            layer.ResizeMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "content":
                            layer.ContentMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "tile":
                            layer.TileMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "click":
                            layer.ClickMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                    }
                }
            }

            foreach (XElement textLabel in layerElement.Elements("TextLabel"))
            {
                layer.AddTextLabel(this.LoadTextLabel(layer, textLabel));
            }

            foreach (XElement sublayer in layerElement.Elements("Layer"))
            {
                layer.AddLayer(this.LoadLayer(layer, sublayer));
            }

            return layer;
        }

        private Layer LoadLayer(Layer parentLayer, XElement layerElement)
        {
            Layer layer = new Layer(parentLayer, this.GetStringAttributeValue(layerElement, "Name"));

            layer.Width = this.GetIntegerAttributeValue(layerElement, "Width");
            layer.Height = this.GetIntegerAttributeValue(layerElement, "Height");
            layer.Alignment = this.AlignmentFromString(this.GetStringAttributeValue(layerElement, "Alignment"));

            string imageName = this.GetStringAttributeValue(layerElement, "Image");
            if (!string.IsNullOrEmpty(imageName))
            {
                layer.Image = layer.Layout.Skin.GetImageReference(imageName);
            }

            foreach (XElement margins in layerElement.Elements("Margins"))
            {
                foreach (XElement margin in margins.Elements())
                {
                    switch (margin.Name.LocalName.ToLowerInvariant())
                    {
                        case "offset":
                            layer.OffsetMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "resize":
                            layer.ResizeMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "content":
                            layer.ContentMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "tile":
                            layer.TileMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                        case "click":
                            layer.ClickMargin = this.LoadMargin(margin.Name.LocalName, margin);
                            break;
                    }
                }
            }

            foreach (XElement textLabel in layerElement.Elements("TextLabel"))
            {
                layer.AddTextLabel(this.LoadTextLabel(layer, textLabel));
            }

            foreach (XElement sublayer in layerElement.Elements("Layer"))
            {
                layer.AddLayer(this.LoadLayer(layer, sublayer));
            }

            return layer;
        }

        private TextLabel LoadTextLabel(Layer layer, XElement textLabel)
        {
            TextLabel label = new TextLabel(layer, this.GetStringAttributeValue(textLabel, "Name"));

            return label;
        }

        private Image LoadImage(Imageset imageSet, XElement imageElement)
        {
            Image image = new Image(imageSet, this.GetStringAttributeValue(imageElement, "Name"));

            float x;
            float y;
            float w;
            float h;

            if (float.TryParse(this.GetStringAttributeValue(imageElement, "X"), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out x))
            {
                if (float.TryParse(this.GetStringAttributeValue(imageElement, "Y"), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out y))
                {
                    if (float.TryParse(this.GetStringAttributeValue(imageElement, "Width"), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out w))
                    {
                        if (float.TryParse(this.GetStringAttributeValue(imageElement, "Height"), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out h))
                        {
                            image.Rectangle = new RectangleF(x, y, w, h);
                        }
                    }
                }
            }

            return image;
        }

        private Margin LoadMargin(string typeName, XElement marginElement)
        {
            Margin margin = new Margin(typeName);

            margin.Left = this.GetFloatAttributeValue(marginElement, "Left");
            margin.Right = this.GetFloatAttributeValue(marginElement, "Right");
            margin.Top = this.GetFloatAttributeValue(marginElement, "Top");
            margin.Bottom = this.GetFloatAttributeValue(marginElement, "Bottom");

            return margin;
        }

        private string GetStringAttributeValue(XElement element, string name)
        {
            XAttribute attribute = null;

            if (element.HasAttributes)
            {
                attribute = element.Attribute(name);
            }

            if (attribute != null)
            {
                return attribute.Value;
            }
            else
            {
                return string.Empty;
            }
        }

        private float GetFloatAttributeValue(XElement element, string name)
        {
            XAttribute attribute = null;

            if (element.HasAttributes)
            {
                attribute = element.Attribute(name);
            }

            if (attribute != null)
            {
                float value;
                if (float.TryParse(attribute.Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out value))
                {
                    return value;
                }
            }

            throw new Exception("unparsable float value: " + attribute.Value);
        }

        /// <summary>
        /// Gets an integer value from a named XDocument attribute
        /// </summary>
        /// <param name="element">XElement to be inspected</param>
        /// <param name="name">Name of the attribute</param>
        /// <returns>returns the integer of the attribute or -1</returns>
        private int GetIntegerAttributeValue(XElement element, string name)
        {
            XAttribute attribute = null;

            if (element.HasAttributes)
            {
                attribute = element.Attribute(name);
            }

            if (attribute != null)
            {
                int value;
                if (int.TryParse(attribute.Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out value))
                {
                    return value;
                }
            }

            return -1;
        }
    }
}
